package jln

import (
	"cid-media-api/api/ocean"
	oceanQCApi "cid-media-api/api/ocean/qianchuan"
	"cid-media-api/global"
	"cid-media-api/models"
	dingTalkRobot "cid-media-api/util/ding-talk-robot"
	"fmt"
	. "github.com/oceanengine/ad_open_sdk_go/models"
	"github.com/robfig/cron/v3"
	"github.com/samber/lo"
	"gorm.io/gorm/clause"
	"log"
	"math"
	"os"
	"strconv"
	"sync"
	"time"
)

func Run(cronSpec string) {
	c := cron.New()
	c.AddFunc(cronSpec, func() {
		// 指定日志文件路径
		logFile, err := os.OpenFile("/www/logs/cid-media-api/crontab/qc_get_material_report_day_jln_"+time.Now().Format(time.DateOnly)+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			log.Fatal(err)
		}
		defer logFile.Close()

		logger := log.New(logFile, "", log.LstdFlags)

		startTime := time.Now()
		logger.Printf("====================================================")
		logger.Printf("正在定时执行极乐鸟千川素材报表拉取任务")
		logger.Printf("任务开始时间： %v", startTime.Format(time.DateTime))

		date := time.Now().Format(time.DateOnly)

		// 如果当前时间是10:00，则执行一次昨天的数据拉取
		if startTime.Hour() == 10 && startTime.Minute() == 00 {
			logger.Printf("当前时间为10:00，正在拉取昨天的回溯数据")
			date = time.Now().AddDate(0, 0, -1).Format(time.DateOnly)
		}

		Execute(0, date, date, logger)

		endTime := time.Now()
		//计算执行时间
		duration := endTime.Sub(startTime)
		logger.Printf("任务执行时间： %v", duration)
		//判读超过3分钟，则发送钉钉通知
		if duration.Minutes() > 3 {
			_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("定时执行极乐鸟千川素材报表拉取任务，执行时间为%v分钟，请检查日志", duration.Minutes()))
		}
		logger.Printf("任务结束时间： %v", endTime.Format(time.DateTime))
		logger.Printf("====================================================")
	})

	c.Run()
}

func Execute(advertiserID int, startDate string, endDate string, logger *log.Logger) {
	logger.Printf("日期范围：从 %s 到 %s\n", startDate, endDate)

	start, _ := time.Parse(time.DateOnly, startDate)
	end, _ := time.Parse(time.DateOnly, endDate)

	// 判断日期顺序
	if start.After(end) {
		logger.Println("开始日期不能晚于结束日期")
		return
	}

	// 遍历日期范围
	current := end
	for !current.Before(start) {
		date := current.Format(time.DateOnly)
		logger.Printf("当前日期: %s\n", date)

		getMaterialReportDataByDate(advertiserID, date, logger)
		current = current.AddDate(0, 0, -1)
	}
}

func getMaterialReportDataByDate(advertiserID int, date string, logger *log.Logger) {
	var mediaAccounts []models.MediaAccount

	insertCount := 0
	updateCount := 0

	// 查询媒体账户 状态为0，媒体ID为1，公司为极乐鸟
	mediaAccountQuery := global.CidDb.
		Model(&models.MediaAccount{}).
		Where("opt_status = ?", 0).
		Where("media_id = ?", ocean.MediaIdOceanQC).
		Where("company_id = ?", 1)
	// 如果提供了advertiserID，进一步过滤查询结果
	if advertiserID > 0 {
		mediaAccountQuery = mediaAccountQuery.Where("advertiser_id = ?", advertiserID)
	}

	// 执行查询，并将结果存入mediaAccounts中，如果出错则打印错误并返回
	err := mediaAccountQuery.Find(&mediaAccounts).Error
	if err != nil {
		print(err)
		return
	}

	// 声明一个WaitGroup，用于等待所有goroutine完成
	var wg sync.WaitGroup
	// 存放API返回的素材报表数据
	var apiData []*models.ReportDayMaterialJln
	apiDataChan := make(chan *models.ReportDayMaterialJln) // 创建通道用于传递数据

	// 遍历每个媒体账户
	for _, account := range mediaAccounts {
		wg.Add(1) // 增加计数器

		// 启动一个goroutine用于获取该媒体账户的素材报表数据
		go func(account *models.MediaAccount) {
			defer wg.Done() // 减少计数器
			const pSize = Enum_100_QianchuanMaterialGetV10PageSize
			var page int32 = 1

			flag := true //控制循环

			for {
				// 如果flag被设置为false, 退出循环
				if !flag {
					break
				}

				// 调用API获取素材报表数据
				res := oceanQCApi.GetMaterialReport(*account, date, page, pSize)

				// 如果返回结果为空或列表长度为0,停止循环
				if res == nil || len(res.AdMaterialInfos) == 0 {
					flag = false // 设置flag为false，终止本次循环
					continue
				}

				page++ // 增加页数 下一页无数据
				if int64(page) > *res.PageInfo.TotalPage {
					flag = false // 设置flag为false, 下一次不再循环
				}

				// 遍历返回的报告数据
				for _, item := range res.AdMaterialInfos {

					materialInfo := item.MaterialInfo.VideoMaterial
					metrics := item.Metrics

					totalPlay := *metrics.TotalPlay
					playDuration3s := *metrics.PlayDuration3s

					playDuration3sRate := 0.0
					if totalPlay > 0 {
						playDuration3sRate = math.Round(playDuration3s/totalPlay) * 100
					}

					imageMode := ""
					if materialInfo.VideoId != nil && *materialInfo.VideoId == "" {
						// videoId为空，认为是无效的视频素材 即使拉取了也无法正常展示
						// 跳过该素材
						continue
					}
					if materialInfo.ImageMode != nil && *materialInfo.ImageMode == VIDEO_VERTICAL_QianchuanMaterialGetV10DataAdMaterialInfosMaterialInfoVideoMaterialImageMode {
						imageMode = "竖版视频"
					}

					formattedRow := models.ReportDayMaterialJln{ // 将数据传递到通道
						ReportDayMaterial: models.ReportDayMaterial{
							MediaId:            account.MediaId,
							CompanyId:          account.CompanyId,
							Tdate:              date,
							MediaAccountId:     account.MediaAccountId,
							AdvertiserId:       account.AdvertiserId,
							AdvertiserName:     account.AdvertiserName,
							PromotionId:        0, //设置为0 为了使唯一索引生效
							MaterialId:         *materialInfo.MaterialId,
							MaterialName:       *materialInfo.Title,
							ImageMode:          imageMode,
							ImgUrl:             *materialInfo.CoverImage.WebUrl,
							VideoId:            *materialInfo.VideoId,
							Click:              int64(*metrics.ClickCnt),
							Cost:               *metrics.StatCost,
							Show:               int64(*metrics.ShowCnt),
							Ctr:                *metrics.Ctr,
							Cpm:                *metrics.CpmPlatform,
							Cpc:                *metrics.CpcPlatform,
							Convert:            int64(*metrics.EcpConvertCnt),
							TotalPlay:          int64(totalPlay),
							ConversionRate:     *metrics.EcpConvertRate,
							ConvertCost:        *metrics.EcpCpaPlatform,
							PayAmountRoi:       *metrics.PrepayAndPayOrderRoi,
							InAppOrderGmv:      strconv.FormatFloat(*metrics.CreateOrderAmount, 'f', 2, 64),
							InAppOrderRoi:      strconv.FormatFloat(*metrics.CreateOrderRoi, 'f', 2, 64),
							PlayDuration3s:     int64(playDuration3s),
							PlayDuration3sRate: playDuration3sRate,
						},
					}

					apiDataChan <- &formattedRow
				}
			}
		}(&account)
	}

	go func() {
		for data := range apiDataChan {
			apiData = append(apiData, data)
		}
		close(apiDataChan) // 确保在所有goroutine完成后关闭通道
	}()

	wg.Wait() // 等待所有相关的goroutine完成

	if len(apiData) == 0 {
		return // 如果apiData切片为空，则直接返回
	}

	var existedRecords []models.ReportDayMaterialJln

	global.CidDb.Model(models.ReportDayMaterialJln{}).
		Where("tdate = ?", date).
		Where("media_id = ?", ocean.MediaIdOceanQC).
		Find(&existedRecords)

	var recordsToInsert []*models.ReportDayMaterialJln
	var recordsToUpdate []*models.ReportDayMaterialJln

	for _, item := range apiData {
		if lo.SomeBy(existedRecords, func(existed models.ReportDayMaterialJln) bool {
			return existed.AdvertiserId == item.AdvertiserId &&
				existed.PromotionId == item.PromotionId &&
				existed.MaterialId == item.MaterialId &&
				lo.Substring(existed.Tdate, 0, 10) == item.Tdate &&
				existed.MediaId == item.MediaId
		}) {
			// 如果存在相同的记录，则添加进更新列表
			recordsToUpdate = append(recordsToUpdate, item)
		} else {
			// 如果不存在相同的记录，则添加进插入列表
			recordsToInsert = append(recordsToInsert, item)
		}
	}

	// 分批更新, 一次性更新太多的话, 可能报错, Error 1390 (HY000): Prepared statement contains too many placeholders
	// 这是由于 mysql 的限制, 占位符的数量不能超过 65535, 更新m条数据, 有n列的话, 最多 m * n 不能超过 65535, 所以这里分批更新
	if len(recordsToUpdate) > 0 {
		//分批更新已存在的记录
		chunks := lo.Chunk(recordsToUpdate, 1000)
		for _, chunk := range chunks {

			err = global.CidDb.Model(models.ReportDayMaterialJln{}).Clauses(clause.OnConflict{
				Columns: []clause.Column{
					{Name: "tdate"},
					{Name: "media_id"},
					{Name: "advertiser_id"},
					{Name: "promotion_id"},
					{Name: "material_id"},
				},
				DoUpdates: clause.AssignmentColumns([]string{
					"cost",
					"show",
					"cpm",
					"click",
					"ctr",
					"cpc",
					"convert",
					"convert_cost",
					"conversion_rate",
					"pay_amount_roi",
					"video_id",
					"image_mode",
					"order_count",
					"order_cost",
					"in_app_order_gmv",
					"in_app_order_roi",
					"play_duration_3s",
					"play_duration_3s_rate",
					"total_play",
				}),
			}).Create(&chunk).Error // 更新已存在的记录
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟素材，更新失败，错误信息：%v", err))
				logger.Printf("更新失败：%v", err)
			}
		}
		updateCount += len(recordsToUpdate) // 增加更新计数
	}

	if len(recordsToInsert) > 0 {
		//将recordsToInsert切片分为多个切片，每1000条记录提交一次
		chunks := lo.Chunk(recordsToInsert, 1000)
		for _, chunk := range chunks {
			err = global.CidDb.Model(models.ReportDayMaterialJln{}).Create(&chunk).Error
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟素材，插入失败，错误信息：%v", err))
				logger.Printf("插入失败：%v", err)
			}
		}
		insertCount += len(recordsToInsert) // 增加插入计数
	}

	logger.Printf("插入数量：%v", insertCount)
	logger.Printf("更新数量：%v", updateCount)
}
